.. _Интерфейсы:

Интерфейсы
===========

.. index::
    interface
    интерфейс

Ключевое слово interface предназначено для определения интерфейсов в иерархии классов.
очень похож на класс со следующими ограничениями:
* Функции-члены, которые он объявляет (но не реализует), являются абстрактными даже
даже без ключевого слова abstract.
* Функции-члены, которые он реализует, должны быть статическими или конечными. (Статические
и финальные функции-члены объясняются ниже).
* Его переменные-члены должны быть статическими.
* Интерфейсы могут наследовать только интерфейсы.
Несмотря на эти ограничения, количество интерфейсов, от которых может наследовать класс, не ограничено.
класс может наследовать. (В отличие от этого, класс может наследовать не более чем от одного класса).

Определение
-----------

Интерфейсы определяются ключевым словом interface, так же, как и классы:

.. code-block:: d

    interface SoundEmitter
    {
    // ...
    }

Интерфейс служит для объявления функций-членов, которые неявно являются абстрактными:

.. code-block:: d

    interface SoundEmitter
    {
        string emitSound(); // Объявлено (не реализовано)
    }

Классы, наследующие от этого интерфейса, должны будут предоставлять
реализации абстрактных функций интерфейса.
Объявления функций интерфейса могут содержать блоки контрактов in и out:

.. code-block:: d

    interface I
    {
        int func(int i)
        in
        {
            /* Strictest requirements that the callers of this
            * function must meet. (Derived interfaces and classes
            * can loosen these requirements.) */
        }
        out
        { // (optionally with (result) parameter)
            /* Exit guarantees that the implementations of this
            * function must give. (Derived interfaces and classes
            * can give additional guarantees.) */
        }
    }

Примеры наследования по контракту мы рассмотрим позже в главе "Контрактное программирование
для структур и классов (:ref:`Контрактное программирование для структур и классов<Контрактное программирование для структур и классов>`).

.. index::
    наследование интерфейса

Наследование от интерфейса
--------------------------

Синтаксис наследования от интерфейса такой же, как и при наследовании от класса:

.. code-block:: d

    class Violin : SoundEmitter
    {
        string emitSound()
        {
            return "TODO insertar note";
        }
    }

    class Bell : SoundEmitter
    {
        string emitSound()
        {
            return "ding";
        }
    }


Интерфейсы поддерживают полиморфизм: Функции, принимающие параметры интерфейса, могут
использовать эти параметры без необходимости знать реальные типы объектов. Например,
например, следующая функция, принимающая параметр SoundEmitter, вызывает
emitSound() для этого параметра без необходимости знать реальный тип объекта
объекта:

.. code-block:: d

    void useSoundEmittingObject(SoundEmitter object)
    {
    // ... some operations ...
        writeln(object.emitSound());
    // ... more operations ...
    }

Как и в случае с классами, эта функция может быть вызвана с любым типом объекта, который
наследует интерфейс SoundEmitter:

.. code-block:: d

    useSoundEmittingObject(new Violin);
    useSoundEmittingObject(new Bell);

Для каждого объекта будет вызвана специальная функция emitSound() и выведены
будут выведены результаты Violin.emitSound и Bell.emitSound:

.. code-block:: console

    todo
    ding

Наследование более чем от одного интерфейса
-------------------------------------------

Класс может наследоваться не более чем от одного класса. Не существует ограничений на количество
интерфейсов, от которых можно наследовать.
Рассмотрим следующий интерфейс, представляющий коммуникационные устройства

.. code-block:: d

    interface CommunicationDevice
    {
        void talk(string message);
        string listen();
    }

Если класс Phone должен использоваться и как проигрыватель звука, и как устройство связи
то он может наследовать оба этих интерфейса:

.. code-block:: d

    class Phone : SoundEmitter, CommunicationDevice {
    // ...
    }

Это определение отражает оба этих отношения: "телефон - это проигрыватель звука" и "телефон - устройство связи".
Чтобы построить объекты этого класса, Phone должен реализовать абстрактные
функции обоих интерфейсов:

.. code-block:: d

    class Phone : SoundEmitter, CommunicationDevice
    {
        string emitSound()
        { // for SoundEmitter
            return "rrring";
        }

        void talk(string message)
        { // for CommunicationDevice
            // ... put the message on the line ...
        }

        string listen()
        { // for CommunicationDevice
            string soundOnTheLine;
            // ... get the message from the line ...
            return soundOnTheLine;
        }
    }

Класс может наследоваться от любого количества интерфейсов, если это имеет смысл в соответствии с
в соответствии с дизайном программы.

Наследование от интерфейса и класса
-----------------------------------

Классы могут наследоваться не более чем от одного класса:

.. code-block:: d

    class Clock
    {
        // ... clock implementation ...
    }

    class AlarmClock : Clock, SoundEmitter
    {
        string emitSound()
        {
            return "beep";
        }
    }


AlarmClock inherits the members of Clock. Additionally, it also provides the
emitSound() function that the SoundEmitter interface requires.

Наследование интерфейса от интерфейса
-------------------------------------

Интерфейс, наследуемый от другого интерфейса, эффективно увеличивает
количество функций, которые должны реализовать подклассы:

.. code-block:: d

    interface MusicalInstrument : SoundEmitter
    {
        void adjustTuning();
    }

Согласно приведенному выше определению, для того чтобы быть музыкальным инструментом (MusicalInstrument), необходимо, чтобы и функция
функция emitSound(), которую требует SoundEmitter, и функция adjustTuning()
которые требует MusicalInstrument, должны быть реализованы.
Например, если Violin наследовалась от MusicalInstrument, то теперь, она также должна реализовать функцию adjustTuning():

.. code-block:: d

    class Violin : MusicalInstrument
    {
        string emitSound()
        { // for SoundEmitter
            return "TODO";
        }

        void adjustTuning()
        { // for MusicalInstrument
            // ... special tuning of the violin ...
        }
    }

.. index::
    статические функции-члены

Cтатические функции-члены
-------------------------

Я отложил объяснение функций-членов static до этой главы, чтобы сделать предыдущие главы короче.
статические функции-члены доступны для структур,
классов и интерфейсов. Обычные функции-члены всегда вызываются на объекте. Член
переменные, на которые ссылается функция-член, являются членами
конкретного объекта:

.. code-block:: d

    struct Foo
    {
        int i;
        void modify(int value)
        {
            i = value;
        }
    }

    void main()
    {
        auto object0 = Foo();
        auto object1 = Foo();
        object0.modify(10); // object0.i changes
        object1.modify(10); // object1.i changes
    }


На членов можно также ссылаться с помощью этой ссылки:

.. code-block:: d

    void modify(int value)
    {
        this.i = value; // equivalent of the previous one
    }

Статическая функция-член не оперирует объектом; не существует объекта, на который
на который ссылалось бы ключевое слово this, поэтому оно не может быть использовано внутри статической функции.
По этой причине ни одна из обычных переменных-членов не доступна внутри статических функций-членов
функций-членов:

.. code-block:: d

    struct Foo
    {
        int i;
        static void commonFunction(int value)
        {
            i = value; // ← ошибка компиляции
            this.i = value; // ← ошибка компиляции
        }
    }


статические функции-члены могут использовать только статические переменные-члены.
Давайте переделаем структуру Point, которую мы рассматривали ранее в главе Структуры
(:ref:`Структуры<Структуры>`), на этот раз со статической функцией-членом. В следующем коде каждый
Point получает уникальный идентификатор, который определяется статической функцией-членом:

.. code-block:: d

    import std.stdio;

    struct Point
    {
        size_t id; // Object id
        int line;
        int column;
        // The id to be used for the next object
        static size_t nextId;
        this(int line, int column)
        {
            this.line = line;
            this.column = column;
            this.id = makeNewId();
        }

        static size_t makeNewId()
        {
            immutable newId = nextId;
            ++nextId;
            return newId;
        }
    }

    void main()
    {
        auto top = Point(7, 0);
        auto middle = Point(8, 0);
        auto bottom = Point(9, 0);
        writeln(top.id);
        writeln(middle.id);
        writeln(bottom.id);
    }

Статическая функция makeNewId() может использовать общую переменную nextId. В результате
в результате каждый объект получает уникальный идентификатор:

.. code-block:: console

    0
    1
    2

Хотя в приведенном примере используется структура, статические функции-члены
доступны также для классов и интерфейсов.

.. index::
    final функции-члены

Final функции-члены
--------------------

Я отложил объяснение final функций-членов до этой главы, чтобы сократить
предыдущие главы. final функции-члены актуальны только для классов
и интерфейсов, поскольку структуры не поддерживают наследование.
final указывает, что функция-член не может быть переопределена подклассом. В некотором
смысле, реализация, предоставляемая этим классом или интерфейсом, является окончательной
реализацией этой функции. Примером случая, когда эта функция
полезна, является ситуация, когда общие шаги алгоритма определяются интерфейсом, а
более мелкие детали остаются для подклассов.
Давайте посмотрим пример этого с игровым интерфейсом. Общие этапы прохождения
игры определяются функцией play() следующего интерфейса:

.. code-block:: d

   interface Game
    {

        final void play()
        {
            string name = gameName();
            writefln("Starting %s", name);
            introducePlayers();
            prepare();
            begin();
            end();
            writefln("Ending %s", name);
        }

        string gameName();
        void introducePlayers();
        void prepare();
        void begin();
        void end);
    }


Подклассы не могут изменять определение функции-члена play()
функции. Подклассы могут (и должны) предоставлять определения пяти
абстрактных функций-членов, которые объявлены интерфейсом. Делая это, подклассы
подклассы завершают недостающие шаги алгоритма:

.. code-block::

    import std.stdio;
    import std.string;
    import std.random;
    import std.conv;

    class DiceSummingGame : Game
    {
        string player;
        size_t count;
        size_t sum;

        string gameName()
        {
            return "Dice Summing Game";
        }

        void introducePlayers()
        {
            write("What is your name? ");
            player = strip(readln());
        }

        void prepare()
        {
            write("How many times to throw the dice? ");
            readf(" %s", &count);
            sum = 0;
        }

        void begin()
        {
            foreach (i; 0 .. count)
            {
                immutable dice = uniform(1, 7);
                writefln("%s: %s", i, dice);
                sum += dice;
            }
        }

        void end()
        {
            writefln("Player: %s, Dice sum: %s, Average: %s",
                player, sum, to!double(sum) / count);
        }
    }

    void useGame(Game game)
    {
        game.play();
    }

    void main()
    {
        useGame(new DiceSummingGame());
    }


Хотя приведенный пример содержит интерфейс, final функции-члены
доступны и для классов.

Как использовать
----------------

Интерфейс - это часто используемая функция (TODO clarify). Почти в каждой иерархии классов есть один или несколько интерфейсов
на вершине почти каждой иерархии классов. Один из видов иерархии, которая часто встречается в программах, включает в себя один интерфейс и несколько классов
которые реализуют этот интерфейс:

.. image:: images/57.8_1.png

Хотя на практике встречаются и более сложные иерархии, простая
иерархия, приведенная выше, решает многие проблемы.
Также принято переносить общие детали реализации иерархий классов
в промежуточные классы. Подклассы наследуются от этих промежуточных классов.
Классы StringInstrument и WindInstrument, приведенные ниже, могут содержатьобщие члены своих соответствующих подклассов:


.. image:: images/57.8_2.png

Абстракция
-----------

Интерфейсы помогают сделать части программ независимыми друг от друга. Это
называется абстракцией. Например, программа, работающая с музыкальными инструментами
может быть написана в основном с использованием интерфейса MusicalInstrument, не указывая реальные типы музыкальных инструментов.

.. code-block:: d

    class Musician
    {
        MusicalInstrument instrument;
        // ...
    }

Различные типы музыкальных инструментов могут быть объединены в коллекции без
без учета фактических типов этих инструментов:

.. code-block:: d

    MusicalInstrument[] orchestraInstruments

Большинство функций программы можно написать только с помощью этого интерфейса:

.. code-block:: d

    bool needsTuning(MusicalInstrument instrument)
    {
        bool result;
        // ...
        return result;
    }

    void playInTune(MusicalInstrument instrument)
    {
        if (needsTuning(instrument))
        {
            instrument.adjustTuning();
        }
        writeln(instrument.emitSound());
    }


Абстрагирование частей программы друг от друга позволяет вносить изменения в
одной части программы без необходимости модифицировать другие части. Когда
реализации определенных частей программы отстают от конкретного интерфейса,
код, использующий только этот интерфейс, не подвергается воздействию

Пример
-------

В следующей программе определены интерфейсы SoundEmitter, MusicalInstrument и
интерфейсы CommunicationDevice:

.. code-block:: d

    import std.stdio;

    /* This interface requires emitSound(). */
    interface SoundEmitter
    {
        string emitSound();
    }

    /* This class needs to implement only emitSound(). */
    class Bell : SoundEmitter
    {
        string emitSound()
        {
            return "ding";
        }
    }

    /* This interface additionally requires adjustTuning(). */
    interface MusicalInstrument : SoundEmitter
    {
        void adjustTuning();
    }

    /* This class needs to implement both emitSound() and
        * adjustTuning(). */
    class Violin : MusicalInstrument
    {
        string emitSound()
        {
            return "TODO insertar notes";
        }

        void adjustTuning()
        {
            // ... tuning of the violin ...
        }
    }

    /* This interface requires talk() and listen(). */
    interface CommunicationDevice
    {
        void talk(string message);
        string listen();
    }

    /* This class needs to implement emitSound(), talk(), and
        * listen(). */
    class Phone : SoundEmitter, CommunicationDevice
    {
        string emitSound()
        {
            return "rrring";
        }

        void talk(string message)
        {
            // ... put the message on the line ...
        }

        string listen()
        {
            string soundOnTheLine;
            // ... get the message from the line ...
            return soundOnTheLine;
        }
    }

    class Clock
    {
        // ... the implementation of Clock ...
    }

    /* This class needs to implement only emitSound(). */
    class AlarmClock : Clock, SoundEmitter
    {
        string emitSound()
        {
            return "beep";
        }
        // ... the implementation of AlarmClock ...
    }

    void main()
    {
        SoundEmitter[] devices;
        devices ~= new Bell;
        devices ~= new Violin;
        devices ~= new Phone;
        devices ~= new AlarmClock;
        foreach (device; devices)
        {
            writeln(device.emitSound());
        }
    }

Поскольку устройства - это фрагмент SoundEmitter, он может содержать объекты любого типа, который
наследуется от SoundEmitter (т.е. типы, которые имеют отношение "is a" с
SoundEmitter). В результате вывод программы состоит из различных
звуков, издаваемых различными типами объектов:

.. code-block:: console

    ding
    TODO insertart notes
    rrring
    beep

Резюме
-------

* Интерфейс похож на класс, который состоит только из абстрактных функций.
  Интерфейс может иметь статические переменные-члены и статические или конечные функции-члены
  функции.
* Чтобы класс был конструируемым, он должен иметь реализации для всех функций-членов
  всех функций-членов всех интерфейсов, от которых он наследует.
* Можно наследовать от неограниченного числа интерфейсов.
* Общая иерархия состоит из одного интерфейса и нескольких
  подклассов, которые реализуют этот интерфейс.

